/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
 */
//
#include "ui_local.h"

void GraphicsOptions_MenuInit(void);

/*
=======================================================================

DRIVER INFORMATION MENU

=======================================================================
 */


#define DRIVERINFO_FRAMEL	"menu/art/frame2_l"
#define DRIVERINFO_FRAMER	"menu/art/frame1_r"
#define DRIVERINFO_BACK0	"menu/art/back_0"
#define DRIVERINFO_BACK1	"menu/art/back_1"

static char* driverinfo_artlist[] = {
    DRIVERINFO_FRAMEL,
    DRIVERINFO_FRAMER,
    DRIVERINFO_BACK0,
    DRIVERINFO_BACK1,
    NULL,
};

#define ID_DRIVERINFOBACK	100

typedef struct {
    menuframework_s menu;
    menutext_s banner;
    menubitmap_s back;
    menubitmap_s framel;
    menubitmap_s framer;
    char stringbuff[1024];
    char* strings[64];
    int numstrings;
} driverinfo_t;

static driverinfo_t s_driverinfo;

/*
=================
DriverInfo_Event
=================
 */
static void DriverInfo_Event(void* ptr, int event) {
    if (event != QM_ACTIVATED)
        return;

    switch (((menucommon_s*) ptr)->id) {
        case ID_DRIVERINFOBACK:
            UI_PopMenu();
            break;
    }
}

/*
=================
DriverInfo_MenuDraw
=================
 */
static void DriverInfo_MenuDraw(void) {
    int i;
    int y;

    Menu_Draw(&s_driverinfo.menu);

    UI_DrawString(320, 80, "VENDOR", UI_CENTER | UI_SMALLFONT, color_red);
    UI_DrawString(320, 152, "PIXELFORMAT", UI_CENTER | UI_SMALLFONT, color_red);
    UI_DrawString(320, 192, "EXTENSIONS", UI_CENTER | UI_SMALLFONT, color_red);

    UI_DrawString(320, 80 + 16, uis.glconfig.vendor_string, UI_CENTER | UI_SMALLFONT, text_color_normal);
    UI_DrawString(320, 96 + 16, uis.glconfig.version_string, UI_CENTER | UI_SMALLFONT, text_color_normal);
    UI_DrawString(320, 112 + 16, uis.glconfig.renderer_string, UI_CENTER | UI_SMALLFONT, text_color_normal);
    UI_DrawString(320, 152 + 16, va("color(%d-bits) Z(%d-bits) stencil(%d-bits)", uis.glconfig.colorBits, uis.glconfig.depthBits, uis.glconfig.stencilBits), UI_CENTER | UI_SMALLFONT, text_color_normal);

    // double column
    y = 192 + 16;
    for (i = 0; i < s_driverinfo.numstrings / 2; i++) {
        UI_DrawString(320 - 4, y, s_driverinfo.strings[i * 2], UI_RIGHT | UI_SMALLFONT, text_color_normal);
        UI_DrawString(320 + 4, y, s_driverinfo.strings[i * 2 + 1], UI_LEFT | UI_SMALLFONT, text_color_normal);
        y += SMALLCHAR_HEIGHT;
    }

    if (s_driverinfo.numstrings & 1)
        UI_DrawString(320, y, s_driverinfo.strings[s_driverinfo.numstrings - 1], UI_CENTER | UI_SMALLFONT, text_color_normal);
}

/*
=================
DriverInfo_Cache
=================
 */
void DriverInfo_Cache(void) {
    int i;

    // touch all our pics
    for (i = 0;; i++) {
        if (!driverinfo_artlist[i])
            break;
        trap_R_RegisterShaderNoMip(driverinfo_artlist[i]);
    }
}

/*
=================
UI_DriverInfo_Menu
=================
 */
static void UI_DriverInfo_Menu(void) {
    char* eptr;
    int i;
    int len;

    // zero set all our globals
    memset(&s_driverinfo, 0, sizeof (driverinfo_t));

    DriverInfo_Cache();

    s_driverinfo.menu.fullscreen = qtrue;
    s_driverinfo.menu.draw = DriverInfo_MenuDraw;

    s_driverinfo.banner.generic.type = MTYPE_BTEXT;
    s_driverinfo.banner.generic.x = 320;
    s_driverinfo.banner.generic.y = 16;
    s_driverinfo.banner.string = "DRIVER INFO";
    s_driverinfo.banner.color = color_white;
    s_driverinfo.banner.style = UI_CENTER;

    s_driverinfo.framel.generic.type = MTYPE_BITMAP;
    s_driverinfo.framel.generic.name = DRIVERINFO_FRAMEL;
    s_driverinfo.framel.generic.flags = QMF_INACTIVE;
    s_driverinfo.framel.generic.x = 0;
    s_driverinfo.framel.generic.y = 78;
    s_driverinfo.framel.width = 256;
    s_driverinfo.framel.height = 329;

    s_driverinfo.framer.generic.type = MTYPE_BITMAP;
    s_driverinfo.framer.generic.name = DRIVERINFO_FRAMER;
    s_driverinfo.framer.generic.flags = QMF_INACTIVE;
    s_driverinfo.framer.generic.x = 376;
    s_driverinfo.framer.generic.y = 76;
    s_driverinfo.framer.width = 256;
    s_driverinfo.framer.height = 334;

    s_driverinfo.back.generic.type = MTYPE_BITMAP;
    s_driverinfo.back.generic.name = DRIVERINFO_BACK0;
    s_driverinfo.back.generic.flags = QMF_LEFT_JUSTIFY | QMF_PULSEIFFOCUS;
    s_driverinfo.back.generic.callback = DriverInfo_Event;
    s_driverinfo.back.generic.id = ID_DRIVERINFOBACK;
    s_driverinfo.back.generic.x = 0;
    s_driverinfo.back.generic.y = 480 - 64;
    s_driverinfo.back.width = 128;
    s_driverinfo.back.height = 64;
    s_driverinfo.back.focuspic = DRIVERINFO_BACK1;

    // TTimo: overflow with particularly long GL extensions (such as the gf3)
    // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=399
    // NOTE: could have pushed the size of stringbuff, but the list is already out of the screen
    // (no matter what your resolution)
    Q_strncpyz(s_driverinfo.stringbuff, uis.glconfig.extensions_string, 1024);

    // build null terminated extension strings
    eptr = s_driverinfo.stringbuff;
    while (s_driverinfo.numstrings < 40 && *eptr) {
        while (*eptr && *eptr == ' ')
            *eptr++ = '\0';

        // track start of valid string
        if (*eptr && *eptr != ' ')
            s_driverinfo.strings[s_driverinfo.numstrings++] = eptr;

        while (*eptr && *eptr != ' ')
            eptr++;
    }

    // safety length strings for display
    for (i = 0; i < s_driverinfo.numstrings; i++) {
        len = strlen(s_driverinfo.strings[i]);
        if (len > 32) {
            s_driverinfo.strings[i][len - 1] = '>';
            s_driverinfo.strings[i][len] = '\0';
        }
    }

    Menu_AddItem(&s_driverinfo.menu, &s_driverinfo.banner);
    Menu_AddItem(&s_driverinfo.menu, &s_driverinfo.framel);
    Menu_AddItem(&s_driverinfo.menu, &s_driverinfo.framer);
    Menu_AddItem(&s_driverinfo.menu, &s_driverinfo.back);

    UI_PushMenu(&s_driverinfo.menu);
}

/*
=======================================================================

GRAPHICS OPTIONS MENU

=======================================================================
 */

#define GRAPHICSOPTIONS_FRAMEL	"menu/art/frame2_l"
#define GRAPHICSOPTIONS_FRAMER	"menu/art/frame1_r"
#define GRAPHICSOPTIONS_BACK0	"menu/art/back_0"
#define GRAPHICSOPTIONS_BACK1	"menu/art/back_1"
#define GRAPHICSOPTIONS_ACCEPT0	"menu/art/accept_0"
#define GRAPHICSOPTIONS_ACCEPT1	"menu/art/accept_1"

#define ID_BACK2	101
#define ID_FULLSCREEN	102
#define ID_LIST		103
#define ID_MODE		104
#define ID_DRIVERINFO	105
#define ID_GRAPHICS	106
#define ID_DISPLAY	107
#define ID_SOUND	108
#define ID_RATIO	109

typedef struct {
    menuframework_s menu;

    menutext_s banner;
    menubitmap_s framel;
    menubitmap_s framer;

    menutext_s graphics;
    menutext_s display;
    menutext_s sound;

    menulist_s list;
    menulist_s ratio;
    menulist_s mode;
    menulist_s driver;
    menuslider_s tq;
    menulist_s fs;
    menulist_s lighting;
    menulist_s allow_extensions;
    menulist_s texturebits;
    menulist_s colordepth;
    menulist_s geometry;
    menulist_s filter;
    menutext_s driverinfo;

    menubitmap_s apply;
    menubitmap_s back;
} graphicsoptions_t;

typedef struct {
    int mode;
    qboolean fullscreen;
    int tq;
    int lighting;
    int colordepth;
    int texturebits;
    int geometry;
    int filter;
    int driver;
    qboolean extensions;
} InitialVideoOptions_s;

static InitialVideoOptions_s s_ivo;
static graphicsoptions_t s_graphicsoptions;

static InitialVideoOptions_s s_ivo_templates[] = {
    {
        6, qtrue, 3, 0, 2, 2, 2, 1, 0, qtrue
    },
    {
        4, qtrue, 2, 0, 2, 2, 1, 1, 0, qtrue // JDC: this was tq 3
    },
    {
        3, qtrue, 2, 0, 0, 0, 1, 0, 0, qtrue
    },
    {
        2, qtrue, 1, 0, 1, 0, 0, 0, 0, qtrue
    },
    {
        2, qtrue, 1, 1, 1, 0, 0, 0, 0, qtrue
    },
    {
        3, qtrue, 1, 0, 0, 0, 1, 0, 0, qtrue
    }
};

#define NUM_IVO_TEMPLATES ( sizeof( s_ivo_templates ) / sizeof( s_ivo_templates[0] ) )

static const char *builtinResolutions[ ] = {
    "320x240",
    "400x300",
    "512x384",
    "640x480",
    "800x600",
    "960x720",
    "1024x768",
    "1152x864",
    "1280x1024",
    "1600x1200",
    "2048x1536",
    "856x480",
    NULL
};

static const char *knownRatios[ ][2] = {
    { "1.25:1", "5:4"},
    { "1.33:1", "4:3"},
    { "1.50:1", "3:2"},
    { "1.56:1", "14:9"},
    { "1.60:1", "16:10"},
    { "1.67:1", "5:3"},
    { "1.78:1", "16:9"},
    { NULL, NULL}
};

#define MAX_RESOLUTIONS	32

static const char* ratios[ MAX_RESOLUTIONS ];
static char ratioBuf[ MAX_RESOLUTIONS ][ 8 ];
static int ratioToRes[ MAX_RESOLUTIONS ];
static int resToRatio[ MAX_RESOLUTIONS ];

static char resbuf[ MAX_STRING_CHARS ];
static const char* detectedResolutions[ MAX_RESOLUTIONS ];

static const char** resolutions = builtinResolutions;
static qboolean resolutionsDetected = qfalse;

/*
=================
GraphicsOptions_FindBuiltinResolution
=================
 */
static int GraphicsOptions_FindBuiltinResolution(int mode) {
    int i;

    if (!resolutionsDetected)
        return mode;

    if (mode < 0)
        return -1;

    for (i = 0; builtinResolutions[ i ]; i++) {
        if (!Q_stricmp(builtinResolutions[ i ], detectedResolutions[ mode ]))
            return i;
    }

    return -1;
}

/*
=================
GraphicsOptions_FindDetectedResolution
=================
 */
static int GraphicsOptions_FindDetectedResolution(int mode) {
    int i;

    if (!resolutionsDetected)
        return mode;

    if (mode < 0)
        return -1;

    for (i = 0; detectedResolutions[ i ]; i++) {
        if (!Q_stricmp(builtinResolutions[ mode ], detectedResolutions[ i ]))
            return i;
    }

    return -1;
}

/*
=================
GraphicsOptions_GetAspectRatios
=================
 */
static void GraphicsOptions_GetAspectRatios(void) {
    int i, r;

    // build ratio list from resolutions
    for (r = 0; resolutions[r]; r++) {
        int w, h;
        char *x;
        char str[ sizeof (ratioBuf[0]) ];

        // calculate resolution's aspect ratio
        x = strchr(resolutions[r], 'x') + 1;
        Q_strncpyz(str, resolutions[r], x - resolutions[r]);
        w = atoi(str);
        h = atoi(x);
        Com_sprintf(str, sizeof (str), "%.2f:1", (float) w / (float) h);

        // add ratio to list if it is new
        // establish res/ratio relationship
        for (i = 0; ratioBuf[i][0]; i++) {
            if (!Q_stricmp(str, ratioBuf[i]))
                break;
        }
        if (!ratioBuf[i][0]) {
            Q_strncpyz(ratioBuf[i], str, sizeof (ratioBuf[i]));
            ratioToRes[i] = r;
        }
        resToRatio[r] = i;
    }

    // prepare itemlist pointer array
    // rename common ratios ("1.33:1" -> "4:3")
    for (r = 0; ratioBuf[r][0]; r++) {
        for (i = 0; knownRatios[i][0]; i++) {
            if (!Q_stricmp(ratioBuf[r], knownRatios[i][0])) {
                Q_strncpyz(ratioBuf[r], knownRatios[i][1], sizeof (ratioBuf[r]));
                break;
            }
        }
        ratios[r] = ratioBuf[r];
    }
    ratios[r] = NULL;
}

/*
=================
GraphicsOptions_GetInitialVideo
=================
 */
static void GraphicsOptions_GetInitialVideo(void) {
    s_ivo.colordepth = s_graphicsoptions.colordepth.curvalue;
    s_ivo.driver = s_graphicsoptions.driver.curvalue;
    s_ivo.mode = s_graphicsoptions.mode.curvalue;
    s_ivo.fullscreen = s_graphicsoptions.fs.curvalue;
    s_ivo.extensions = s_graphicsoptions.allow_extensions.curvalue;
    s_ivo.tq = s_graphicsoptions.tq.curvalue;
    s_ivo.lighting = s_graphicsoptions.lighting.curvalue;
    s_ivo.geometry = s_graphicsoptions.geometry.curvalue;
    s_ivo.filter = s_graphicsoptions.filter.curvalue;
    s_ivo.texturebits = s_graphicsoptions.texturebits.curvalue;
}

/*
=================
GraphicsOptions_GetResolutions
=================
 */
static void GraphicsOptions_GetResolutions(void) {
    Q_strncpyz(resbuf, UI_Cvar_VariableString("r_availableModes"), sizeof (resbuf));
    if (*resbuf) {
        char* s = resbuf;
        unsigned int i = 0;
        while (s && i < sizeof (detectedResolutions) / sizeof (detectedResolutions[0]) - 1) {
            detectedResolutions[i++] = s;
            s = strchr(s, ' ');
            if (s)
                *s++ = '\0';
        }
        detectedResolutions[ i ] = NULL;

        if (i > 0) {
            resolutions = detectedResolutions;
            resolutionsDetected = qtrue;
        }
    }
}

/*
=================
GraphicsOptions_CheckConfig
=================
 */
static void GraphicsOptions_CheckConfig(void) {
    int i;

    for (i = 0; i < NUM_IVO_TEMPLATES - 1; i++) {
        if (s_ivo_templates[i].colordepth != s_graphicsoptions.colordepth.curvalue)
            continue;
        if (s_ivo_templates[i].driver != s_graphicsoptions.driver.curvalue)
            continue;
        if (GraphicsOptions_FindDetectedResolution(s_ivo_templates[i].mode) != s_graphicsoptions.mode.curvalue)
            continue;
        if (s_ivo_templates[i].fullscreen != s_graphicsoptions.fs.curvalue)
            continue;
        if (s_ivo_templates[i].tq != s_graphicsoptions.tq.curvalue)
            continue;
        if (s_ivo_templates[i].lighting != s_graphicsoptions.lighting.curvalue)
            continue;
        if (s_ivo_templates[i].geometry != s_graphicsoptions.geometry.curvalue)
            continue;
        if (s_ivo_templates[i].filter != s_graphicsoptions.filter.curvalue)
            continue;
        //		if ( s_ivo_templates[i].texturebits != s_graphicsoptions.texturebits.curvalue )
        //			continue;
        s_graphicsoptions.list.curvalue = i;
        return;
    }

    // return 'Custom' ivo template
    s_graphicsoptions.list.curvalue = NUM_IVO_TEMPLATES - 1;
}

/*
=================
GraphicsOptions_UpdateMenuItems
=================
 */
static void GraphicsOptions_UpdateMenuItems(void) {
    if (s_graphicsoptions.driver.curvalue == 1) {
        s_graphicsoptions.fs.curvalue = 1;
        s_graphicsoptions.fs.generic.flags |= QMF_GRAYED;
        s_graphicsoptions.colordepth.curvalue = 1;
    } else {
        s_graphicsoptions.fs.generic.flags &= ~QMF_GRAYED;
    }

    if (s_graphicsoptions.fs.curvalue == 0 || s_graphicsoptions.driver.curvalue == 1) {
        s_graphicsoptions.colordepth.curvalue = 0;
        s_graphicsoptions.colordepth.generic.flags |= QMF_GRAYED;
    } else {
        s_graphicsoptions.colordepth.generic.flags &= ~QMF_GRAYED;
    }

    if (s_graphicsoptions.allow_extensions.curvalue == 0) {
        if (s_graphicsoptions.texturebits.curvalue == 0) {
            s_graphicsoptions.texturebits.curvalue = 1;
        }
    }

    s_graphicsoptions.apply.generic.flags |= QMF_HIDDEN | QMF_INACTIVE;

    if (s_ivo.mode != s_graphicsoptions.mode.curvalue) {
        s_graphicsoptions.apply.generic.flags &= ~(QMF_HIDDEN | QMF_INACTIVE);
    }
    if (s_ivo.fullscreen != s_graphicsoptions.fs.curvalue) {
        s_graphicsoptions.apply.generic.flags &= ~(QMF_HIDDEN | QMF_INACTIVE);
    }
    if (s_ivo.extensions != s_graphicsoptions.allow_extensions.curvalue) {
        s_graphicsoptions.apply.generic.flags &= ~(QMF_HIDDEN | QMF_INACTIVE);
    }
    if (s_ivo.tq != s_graphicsoptions.tq.curvalue) {
        s_graphicsoptions.apply.generic.flags &= ~(QMF_HIDDEN | QMF_INACTIVE);
    }
    if (s_ivo.lighting != s_graphicsoptions.lighting.curvalue) {
        s_graphicsoptions.apply.generic.flags &= ~(QMF_HIDDEN | QMF_INACTIVE);
    }
    if (s_ivo.colordepth != s_graphicsoptions.colordepth.curvalue) {
        s_graphicsoptions.apply.generic.flags &= ~(QMF_HIDDEN | QMF_INACTIVE);
    }
    if (s_ivo.driver != s_graphicsoptions.driver.curvalue) {
        s_graphicsoptions.apply.generic.flags &= ~(QMF_HIDDEN | QMF_INACTIVE);
    }
    if (s_ivo.texturebits != s_graphicsoptions.texturebits.curvalue) {
        s_graphicsoptions.apply.generic.flags &= ~(QMF_HIDDEN | QMF_INACTIVE);
    }
    if (s_ivo.geometry != s_graphicsoptions.geometry.curvalue) {
        s_graphicsoptions.apply.generic.flags &= ~(QMF_HIDDEN | QMF_INACTIVE);
    }
    if (s_ivo.filter != s_graphicsoptions.filter.curvalue) {
        s_graphicsoptions.apply.generic.flags &= ~(QMF_HIDDEN | QMF_INACTIVE);
    }

    GraphicsOptions_CheckConfig();
}

/*
=================
GraphicsOptions_ApplyChanges
=================
 */
static void GraphicsOptions_ApplyChanges(void *unused, int notification) {
    if (notification != QM_ACTIVATED)
        return;

    switch (s_graphicsoptions.texturebits.curvalue) {
        case 0:
            trap_Cvar_SetValue("r_texturebits", 0);
            break;
        case 1:
            trap_Cvar_SetValue("r_texturebits", 16);
            break;
        case 2:
            trap_Cvar_SetValue("r_texturebits", 32);
            break;
    }
    trap_Cvar_SetValue("r_picmip", 3 - s_graphicsoptions.tq.curvalue);
    trap_Cvar_SetValue("r_allowExtensions", s_graphicsoptions.allow_extensions.curvalue);

    if (resolutionsDetected) {
        // search for builtin mode that matches the detected mode
        int mode;
        if (s_graphicsoptions.mode.curvalue == -1
                || s_graphicsoptions.mode.curvalue >= sizeof (detectedResolutions) / sizeof (detectedResolutions[0]))
            s_graphicsoptions.mode.curvalue = 0;

        mode = GraphicsOptions_FindBuiltinResolution(s_graphicsoptions.mode.curvalue);
        if (mode == -1) {
            char w[ 16 ], h[ 16 ];
            Q_strncpyz(w, detectedResolutions[ s_graphicsoptions.mode.curvalue ], sizeof ( w));
            *strchr(w, 'x') = 0;
            Q_strncpyz(h,
                    strchr(detectedResolutions[ s_graphicsoptions.mode.curvalue ], 'x') + 1, sizeof ( h));
            trap_Cvar_Set("r_customwidth", w);
            trap_Cvar_Set("r_customheight", h);
        }

        trap_Cvar_SetValue("r_mode", mode);
    } else
        trap_Cvar_SetValue("r_mode", s_graphicsoptions.mode.curvalue);

    trap_Cvar_SetValue("r_fullscreen", s_graphicsoptions.fs.curvalue);
    trap_Cvar_SetValue("r_colorbits", 0);
    trap_Cvar_SetValue("r_depthbits", 0);
    trap_Cvar_SetValue("r_stencilbits", 0);
    trap_Cvar_SetValue("r_vertexLight", s_graphicsoptions.lighting.curvalue);

    if (s_graphicsoptions.geometry.curvalue == 2) {
        trap_Cvar_SetValue("r_lodBias", 0);
        trap_Cvar_SetValue("r_subdivisions", 4);
    } else if (s_graphicsoptions.geometry.curvalue == 1) {
        trap_Cvar_SetValue("r_lodBias", 1);
        trap_Cvar_SetValue("r_subdivisions", 12);
    } else {
        trap_Cvar_SetValue("r_lodBias", 1);
        trap_Cvar_SetValue("r_subdivisions", 20);
    }

    if (s_graphicsoptions.filter.curvalue) {
        trap_Cvar_Set("r_textureMode", "GL_LINEAR_MIPMAP_LINEAR");
    } else {
        trap_Cvar_Set("r_textureMode", "GL_LINEAR_MIPMAP_NEAREST");
    }

    trap_Cmd_ExecuteText(EXEC_APPEND, "vid_restart\n");
}

/*
=================
GraphicsOptions_Event
=================
 */
static void GraphicsOptions_Event(void* ptr, int event) {
    InitialVideoOptions_s *ivo;

    if (event != QM_ACTIVATED) {
        return;
    }

    switch (((menucommon_s*) ptr)->id) {
        case ID_RATIO:
            s_graphicsoptions.mode.curvalue =
                    ratioToRes[ s_graphicsoptions.ratio.curvalue ];
            // fall through to apply mode constraints

        case ID_MODE:
            // clamp 3dfx video modes
            if (s_graphicsoptions.driver.curvalue == 1) {
                if (s_graphicsoptions.mode.curvalue < 2)
                    s_graphicsoptions.mode.curvalue = 2;
                else if (s_graphicsoptions.mode.curvalue > 6)
                    s_graphicsoptions.mode.curvalue = 6;
            }
            s_graphicsoptions.ratio.curvalue =
                    resToRatio[ s_graphicsoptions.mode.curvalue ];
            break;

        case ID_LIST:
            ivo = &s_ivo_templates[s_graphicsoptions.list.curvalue];

            s_graphicsoptions.mode.curvalue = GraphicsOptions_FindDetectedResolution(ivo->mode);
            s_graphicsoptions.ratio.curvalue =
                    resToRatio[ s_graphicsoptions.mode.curvalue ];
            s_graphicsoptions.tq.curvalue = ivo->tq;
            s_graphicsoptions.lighting.curvalue = ivo->lighting;
            s_graphicsoptions.colordepth.curvalue = ivo->colordepth;
            s_graphicsoptions.texturebits.curvalue = ivo->texturebits;
            s_graphicsoptions.geometry.curvalue = ivo->geometry;
            s_graphicsoptions.filter.curvalue = ivo->filter;
            s_graphicsoptions.fs.curvalue = ivo->fullscreen;
            break;

        case ID_DRIVERINFO:
            UI_DriverInfo_Menu();
            break;

        case ID_BACK2:
            UI_PopMenu();
            break;

        case ID_GRAPHICS:
            break;

        case ID_DISPLAY:
            UI_PopMenu();
            UI_DisplayOptionsMenu();
            break;

        case ID_SOUND:
            UI_PopMenu();
            UI_SoundOptionsMenu();
            break;
    }
}

/*
================
GraphicsOptions_TQEvent
================
 */
static void GraphicsOptions_TQEvent(void *ptr, int event) {
    if (event != QM_ACTIVATED) {
        return;
    }
    s_graphicsoptions.tq.curvalue = (int) (s_graphicsoptions.tq.curvalue + 0.5);
}

/*
================
GraphicsOptions_MenuDraw
================
 */
void GraphicsOptions_MenuDraw(void) {
    //APSFIX - rework this
    GraphicsOptions_UpdateMenuItems();

    Menu_Draw(&s_graphicsoptions.menu);
}

/*
=================
GraphicsOptions_SetMenuItems
=================
 */
static void GraphicsOptions_SetMenuItems(void) {
    s_graphicsoptions.mode.curvalue =
            GraphicsOptions_FindDetectedResolution(trap_Cvar_VariableValue("r_mode"));

    if (s_graphicsoptions.mode.curvalue < 0) {
        if (resolutionsDetected) {
            int i;
            char buf[MAX_STRING_CHARS];
            trap_Cvar_VariableStringBuffer("r_customwidth", buf, sizeof (buf) - 2);
            buf[strlen(buf) + 1] = 0;
            buf[strlen(buf)] = 'x';
            trap_Cvar_VariableStringBuffer("r_customheight", buf + strlen(buf), sizeof (buf) - strlen(buf));

            for (i = 0; detectedResolutions[i]; ++i) {
                if (!Q_stricmp(buf, detectedResolutions[i])) {
                    s_graphicsoptions.mode.curvalue = i;
                    break;
                }
            }
            if (s_graphicsoptions.mode.curvalue < 0)
                s_graphicsoptions.mode.curvalue = 0;
        } else {
            s_graphicsoptions.mode.curvalue = 3;
        }
    }
    s_graphicsoptions.ratio.curvalue =
            resToRatio[ s_graphicsoptions.mode.curvalue ];
    s_graphicsoptions.fs.curvalue = trap_Cvar_VariableValue("r_fullscreen");
    s_graphicsoptions.allow_extensions.curvalue = trap_Cvar_VariableValue("r_allowExtensions");
    s_graphicsoptions.tq.curvalue = 3 - trap_Cvar_VariableValue("r_picmip");
    if (s_graphicsoptions.tq.curvalue < 0) {
        s_graphicsoptions.tq.curvalue = 0;
    } else if (s_graphicsoptions.tq.curvalue > 3) {
        s_graphicsoptions.tq.curvalue = 3;
    }

    s_graphicsoptions.lighting.curvalue = trap_Cvar_VariableValue("r_vertexLight") != 0;
    switch ((int) trap_Cvar_VariableValue("r_texturebits")) {
        default:
        case 0:
            s_graphicsoptions.texturebits.curvalue = 0;
            break;
        case 16:
            s_graphicsoptions.texturebits.curvalue = 1;
            break;
        case 32:
            s_graphicsoptions.texturebits.curvalue = 2;
            break;
    }

    if (!Q_stricmp(UI_Cvar_VariableString("r_textureMode"), "GL_LINEAR_MIPMAP_NEAREST")) {
        s_graphicsoptions.filter.curvalue = 0;
    } else {
        s_graphicsoptions.filter.curvalue = 1;
    }

    if (trap_Cvar_VariableValue("r_lodBias") > 0) {
        if (trap_Cvar_VariableValue("r_subdivisions") >= 20) {
            s_graphicsoptions.geometry.curvalue = 0;
        } else {
            s_graphicsoptions.geometry.curvalue = 1;
        }
    } else {
        s_graphicsoptions.geometry.curvalue = 2;
    }

    switch ((int) trap_Cvar_VariableValue("r_colorbits")) {
        default:
        case 0:
            s_graphicsoptions.colordepth.curvalue = 0;
            break;
        case 16:
            s_graphicsoptions.colordepth.curvalue = 1;
            break;
        case 32:
            s_graphicsoptions.colordepth.curvalue = 2;
            break;
    }

    if (s_graphicsoptions.fs.curvalue == 0) {
        s_graphicsoptions.colordepth.curvalue = 0;
    }
    if (s_graphicsoptions.driver.curvalue == 1) {
        s_graphicsoptions.colordepth.curvalue = 1;
    }
}

/*
================
GraphicsOptions_MenuInit
================
 */
void GraphicsOptions_MenuInit(void) {
    static const char *s_driver_names[] = {
        "Default",
        "Voodoo",
        NULL
    };

    static const char *tq_names[] = {
        "Default",
        "16 bit",
        "32 bit",
        NULL
    };

    static const char *s_graphics_options_names[] = {
        "Very High Quality",
        "High Quality",
        "Normal",
        "Fast",
        "Fastest",
        "Custom",
        NULL
    };

    static const char *lighting_names[] = {
        "Lightmap",
        "Vertex",
        NULL
    };

    static const char *colordepth_names[] = {
        "Default",
        "16 bit",
        "32 bit",
        NULL
    };

    static const char *filter_names[] = {
        "Bilinear",
        "Trilinear",
        NULL
    };
    static const char *quality_names[] = {
        "Low",
        "Medium",
        "High",
        NULL
    };
    static const char *enabled_names[] = {
        "Off",
        "On",
        NULL
    };

    int y;

    // zero set all our globals
    memset(&s_graphicsoptions, 0, sizeof (graphicsoptions_t));

    GraphicsOptions_GetResolutions();
    GraphicsOptions_GetAspectRatios();

    GraphicsOptions_Cache();

    s_graphicsoptions.menu.wrapAround = qtrue;
    s_graphicsoptions.menu.fullscreen = qtrue;
    s_graphicsoptions.menu.draw = GraphicsOptions_MenuDraw;

    s_graphicsoptions.banner.generic.type = MTYPE_BTEXT;
    s_graphicsoptions.banner.generic.x = 320;
    s_graphicsoptions.banner.generic.y = 16;
    s_graphicsoptions.banner.string = "SYSTEM SETUP";
    s_graphicsoptions.banner.color = color_white;
    s_graphicsoptions.banner.style = UI_CENTER;

    s_graphicsoptions.framel.generic.type = MTYPE_BITMAP;
    s_graphicsoptions.framel.generic.name = GRAPHICSOPTIONS_FRAMEL;
    s_graphicsoptions.framel.generic.flags = QMF_INACTIVE;
    s_graphicsoptions.framel.generic.x = 0;
    s_graphicsoptions.framel.generic.y = 78;
    s_graphicsoptions.framel.width = 256;
    s_graphicsoptions.framel.height = 329;

    s_graphicsoptions.framer.generic.type = MTYPE_BITMAP;
    s_graphicsoptions.framer.generic.name = GRAPHICSOPTIONS_FRAMER;
    s_graphicsoptions.framer.generic.flags = QMF_INACTIVE;
    s_graphicsoptions.framer.generic.x = 376;
    s_graphicsoptions.framer.generic.y = 76;
    s_graphicsoptions.framer.width = 256;
    s_graphicsoptions.framer.height = 334;

    s_graphicsoptions.graphics.generic.type = MTYPE_PTEXT;
    s_graphicsoptions.graphics.generic.flags = QMF_RIGHT_JUSTIFY;
    s_graphicsoptions.graphics.generic.id = ID_GRAPHICS;
    s_graphicsoptions.graphics.generic.callback = GraphicsOptions_Event;
    s_graphicsoptions.graphics.generic.x = 216;
    s_graphicsoptions.graphics.generic.y = 240 - 2 * PROP_HEIGHT;
    s_graphicsoptions.graphics.string = "GRAPHICS";
    s_graphicsoptions.graphics.style = UI_RIGHT | UI_SMALLFONT;
    s_graphicsoptions.graphics.color = color_red;

    s_graphicsoptions.display.generic.type = MTYPE_PTEXT;
    s_graphicsoptions.display.generic.flags = QMF_RIGHT_JUSTIFY | QMF_PULSEIFFOCUS;
    s_graphicsoptions.display.generic.id = ID_DISPLAY;
    s_graphicsoptions.display.generic.callback = GraphicsOptions_Event;
    s_graphicsoptions.display.generic.x = 216;
    s_graphicsoptions.display.generic.y = 240 - PROP_HEIGHT;
    s_graphicsoptions.display.string = "DISPLAY";
    s_graphicsoptions.display.style = UI_RIGHT | UI_SMALLFONT;
    s_graphicsoptions.display.color = color_red;

    s_graphicsoptions.sound.generic.type = MTYPE_PTEXT;
    s_graphicsoptions.sound.generic.flags = QMF_RIGHT_JUSTIFY | QMF_PULSEIFFOCUS;
    s_graphicsoptions.sound.generic.id = ID_SOUND;
    s_graphicsoptions.sound.generic.callback = GraphicsOptions_Event;
    s_graphicsoptions.sound.generic.x = 216;
    s_graphicsoptions.sound.generic.y = 240;
    s_graphicsoptions.sound.string = "SOUND";
    s_graphicsoptions.sound.style = UI_RIGHT | UI_SMALLFONT;
    s_graphicsoptions.sound.color = color_red;

    y = 240 - 7 * (BIGCHAR_HEIGHT + 2);
    s_graphicsoptions.list.generic.type = MTYPE_SPINCONTROL;
    s_graphicsoptions.list.generic.name = "Graphics Settings:";
    s_graphicsoptions.list.generic.flags = QMF_PULSEIFFOCUS | QMF_SMALLFONT;
    s_graphicsoptions.list.generic.x = 400;
    s_graphicsoptions.list.generic.y = y;
    s_graphicsoptions.list.generic.callback = GraphicsOptions_Event;
    s_graphicsoptions.list.generic.id = ID_LIST;
    s_graphicsoptions.list.itemnames = s_graphics_options_names;
    y += 2 * (BIGCHAR_HEIGHT + 2);

    s_graphicsoptions.driver.generic.type = MTYPE_SPINCONTROL;
    s_graphicsoptions.driver.generic.name = "GL Driver:";
    s_graphicsoptions.driver.generic.flags = QMF_PULSEIFFOCUS | QMF_SMALLFONT;
    s_graphicsoptions.driver.generic.x = 400;
    s_graphicsoptions.driver.generic.y = y;
    s_graphicsoptions.driver.itemnames = s_driver_names;
    s_graphicsoptions.driver.curvalue = (uis.glconfig.driverType == GLDRV_VOODOO);
    y += BIGCHAR_HEIGHT + 2;

    // references/modifies "r_allowExtensions"
    s_graphicsoptions.allow_extensions.generic.type = MTYPE_SPINCONTROL;
    s_graphicsoptions.allow_extensions.generic.name = "GL Extensions:";
    s_graphicsoptions.allow_extensions.generic.flags = QMF_PULSEIFFOCUS | QMF_SMALLFONT;
    s_graphicsoptions.allow_extensions.generic.x = 400;
    s_graphicsoptions.allow_extensions.generic.y = y;
    s_graphicsoptions.allow_extensions.itemnames = enabled_names;
    y += BIGCHAR_HEIGHT + 2;

    s_graphicsoptions.ratio.generic.type = MTYPE_SPINCONTROL;
    s_graphicsoptions.ratio.generic.name = "Aspect Ratio:";
    s_graphicsoptions.ratio.generic.flags = QMF_PULSEIFFOCUS | QMF_SMALLFONT;
    s_graphicsoptions.ratio.generic.x = 400;
    s_graphicsoptions.ratio.generic.y = y;
    s_graphicsoptions.ratio.itemnames = ratios;
    s_graphicsoptions.ratio.generic.callback = GraphicsOptions_Event;
    s_graphicsoptions.ratio.generic.id = ID_RATIO;
    y += BIGCHAR_HEIGHT + 2;

    // references/modifies "r_mode"
    s_graphicsoptions.mode.generic.type = MTYPE_SPINCONTROL;
    s_graphicsoptions.mode.generic.name = "Resolution:";
    s_graphicsoptions.mode.generic.flags = QMF_PULSEIFFOCUS | QMF_SMALLFONT;
    s_graphicsoptions.mode.generic.x = 400;
    s_graphicsoptions.mode.generic.y = y;
    s_graphicsoptions.mode.itemnames = resolutions;
    s_graphicsoptions.mode.generic.callback = GraphicsOptions_Event;
    s_graphicsoptions.mode.generic.id = ID_MODE;
    y += BIGCHAR_HEIGHT + 2;

    // references "r_colorbits"
    s_graphicsoptions.colordepth.generic.type = MTYPE_SPINCONTROL;
    s_graphicsoptions.colordepth.generic.name = "Color Depth:";
    s_graphicsoptions.colordepth.generic.flags = QMF_PULSEIFFOCUS | QMF_SMALLFONT;
    s_graphicsoptions.colordepth.generic.x = 400;
    s_graphicsoptions.colordepth.generic.y = y;
    s_graphicsoptions.colordepth.itemnames = colordepth_names;
    y += BIGCHAR_HEIGHT + 2;

    // references/modifies "r_fullscreen"
    s_graphicsoptions.fs.generic.type = MTYPE_SPINCONTROL;
    s_graphicsoptions.fs.generic.name = "Fullscreen:";
    s_graphicsoptions.fs.generic.flags = QMF_PULSEIFFOCUS | QMF_SMALLFONT;
    s_graphicsoptions.fs.generic.x = 400;
    s_graphicsoptions.fs.generic.y = y;
    s_graphicsoptions.fs.itemnames = enabled_names;
    y += BIGCHAR_HEIGHT + 2;

    // references/modifies "r_vertexLight"
    s_graphicsoptions.lighting.generic.type = MTYPE_SPINCONTROL;
    s_graphicsoptions.lighting.generic.name = "Lighting:";
    s_graphicsoptions.lighting.generic.flags = QMF_PULSEIFFOCUS | QMF_SMALLFONT;
    s_graphicsoptions.lighting.generic.x = 400;
    s_graphicsoptions.lighting.generic.y = y;
    s_graphicsoptions.lighting.itemnames = lighting_names;
    y += BIGCHAR_HEIGHT + 2;

    // references/modifies "r_lodBias" & "subdivisions"
    s_graphicsoptions.geometry.generic.type = MTYPE_SPINCONTROL;
    s_graphicsoptions.geometry.generic.name = "Geometric Detail:";
    s_graphicsoptions.geometry.generic.flags = QMF_PULSEIFFOCUS | QMF_SMALLFONT;
    s_graphicsoptions.geometry.generic.x = 400;
    s_graphicsoptions.geometry.generic.y = y;
    s_graphicsoptions.geometry.itemnames = quality_names;
    y += BIGCHAR_HEIGHT + 2;

    // references/modifies "r_picmip"
    s_graphicsoptions.tq.generic.type = MTYPE_SLIDER;
    s_graphicsoptions.tq.generic.name = "Texture Detail:";
    s_graphicsoptions.tq.generic.flags = QMF_PULSEIFFOCUS | QMF_SMALLFONT;
    s_graphicsoptions.tq.generic.x = 400;
    s_graphicsoptions.tq.generic.y = y;
    s_graphicsoptions.tq.minvalue = 0;
    s_graphicsoptions.tq.maxvalue = 3;
    s_graphicsoptions.tq.generic.callback = GraphicsOptions_TQEvent;
    y += BIGCHAR_HEIGHT + 2;

    // references/modifies "r_textureBits"
    s_graphicsoptions.texturebits.generic.type = MTYPE_SPINCONTROL;
    s_graphicsoptions.texturebits.generic.name = "Texture Quality:";
    s_graphicsoptions.texturebits.generic.flags = QMF_PULSEIFFOCUS | QMF_SMALLFONT;
    s_graphicsoptions.texturebits.generic.x = 400;
    s_graphicsoptions.texturebits.generic.y = y;
    s_graphicsoptions.texturebits.itemnames = tq_names;
    y += BIGCHAR_HEIGHT + 2;

    // references/modifies "r_textureMode"
    s_graphicsoptions.filter.generic.type = MTYPE_SPINCONTROL;
    s_graphicsoptions.filter.generic.name = "Texture Filter:";
    s_graphicsoptions.filter.generic.flags = QMF_PULSEIFFOCUS | QMF_SMALLFONT;
    s_graphicsoptions.filter.generic.x = 400;
    s_graphicsoptions.filter.generic.y = y;
    s_graphicsoptions.filter.itemnames = filter_names;
    y += 2 * BIGCHAR_HEIGHT;

    s_graphicsoptions.driverinfo.generic.type = MTYPE_PTEXT;
    s_graphicsoptions.driverinfo.generic.flags = QMF_CENTER_JUSTIFY | QMF_PULSEIFFOCUS;
    s_graphicsoptions.driverinfo.generic.callback = GraphicsOptions_Event;
    s_graphicsoptions.driverinfo.generic.id = ID_DRIVERINFO;
    s_graphicsoptions.driverinfo.generic.x = 320;
    s_graphicsoptions.driverinfo.generic.y = y;
    s_graphicsoptions.driverinfo.string = "Driver Info";
    s_graphicsoptions.driverinfo.style = UI_CENTER | UI_SMALLFONT;
    s_graphicsoptions.driverinfo.color = color_red;
    y += BIGCHAR_HEIGHT + 2;

    s_graphicsoptions.back.generic.type = MTYPE_BITMAP;
    s_graphicsoptions.back.generic.name = GRAPHICSOPTIONS_BACK0;
    s_graphicsoptions.back.generic.flags = QMF_LEFT_JUSTIFY | QMF_PULSEIFFOCUS;
    s_graphicsoptions.back.generic.callback = GraphicsOptions_Event;
    s_graphicsoptions.back.generic.id = ID_BACK2;
    s_graphicsoptions.back.generic.x = 0;
    s_graphicsoptions.back.generic.y = 480 - 64;
    s_graphicsoptions.back.width = 128;
    s_graphicsoptions.back.height = 64;
    s_graphicsoptions.back.focuspic = GRAPHICSOPTIONS_BACK1;

    s_graphicsoptions.apply.generic.type = MTYPE_BITMAP;
    s_graphicsoptions.apply.generic.name = GRAPHICSOPTIONS_ACCEPT0;
    s_graphicsoptions.apply.generic.flags = QMF_RIGHT_JUSTIFY | QMF_PULSEIFFOCUS | QMF_HIDDEN | QMF_INACTIVE;
    s_graphicsoptions.apply.generic.callback = GraphicsOptions_ApplyChanges;
    s_graphicsoptions.apply.generic.x = 640;
    s_graphicsoptions.apply.generic.y = 480 - 64;
    s_graphicsoptions.apply.width = 128;
    s_graphicsoptions.apply.height = 64;
    s_graphicsoptions.apply.focuspic = GRAPHICSOPTIONS_ACCEPT1;

    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.banner);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.framel);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.framer);

    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.graphics);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.display);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.sound);

    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.list);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.driver);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.allow_extensions);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.ratio);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.mode);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.colordepth);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.fs);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.lighting);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.geometry);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.tq);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.texturebits);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.filter);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.driverinfo);

    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.back);
    Menu_AddItem(&s_graphicsoptions.menu, (void *) & s_graphicsoptions.apply);

    GraphicsOptions_SetMenuItems();
    GraphicsOptions_GetInitialVideo();

    if (uis.glconfig.driverType == GLDRV_ICD &&
            uis.glconfig.hardwareType == GLHW_3DFX_2D3D) {
        s_graphicsoptions.driver.generic.flags |= QMF_HIDDEN | QMF_INACTIVE;
    }
}

/*
=================
GraphicsOptions_Cache
=================
 */
void GraphicsOptions_Cache(void) {
    trap_R_RegisterShaderNoMip(GRAPHICSOPTIONS_FRAMEL);
    trap_R_RegisterShaderNoMip(GRAPHICSOPTIONS_FRAMER);
    trap_R_RegisterShaderNoMip(GRAPHICSOPTIONS_BACK0);
    trap_R_RegisterShaderNoMip(GRAPHICSOPTIONS_BACK1);
    trap_R_RegisterShaderNoMip(GRAPHICSOPTIONS_ACCEPT0);
    trap_R_RegisterShaderNoMip(GRAPHICSOPTIONS_ACCEPT1);
}

/*
=================
UI_GraphicsOptionsMenu
=================
 */
void UI_GraphicsOptionsMenu(void) {
    GraphicsOptions_MenuInit();
    UI_PushMenu(&s_graphicsoptions.menu);
    Menu_SetCursorToItem(&s_graphicsoptions.menu, &s_graphicsoptions.graphics);
}

